# -*- coding: utf-8 -*-
#############################
####### client module #######
#############################
########## INFO #############
#
# program: chat
#    file: client.py
#  python: version 2.6 is required
#     use: python -i client.py
#   autor: Alexander Anikeev
# version: 0.9.9 RC2
#  update: 26.03.11
#

# message = "#dat" |4 bytes| + "#{size of code and text}" |4 bytes| + "#{code}" |4 bytes| + "#{crc32}" |10 bytes| + "#{text}" |233 bytes|
# message = "#dat#{size of code and text}#{code}#{crc32}#{text}" |256 bytes|


# import all needed modules
from socket import *
from sys import *
from threading import *
from time import *
import socket, sys, threading, time, string
from zlib import crc32
from cPickle import loads

# the needed global variables
global host, port, LOGGING, log, status, err_log, err, code, check, servers, online
check=None
#recieved data
data=[]
#actial topic
topic = []
#online users
online = []
# switch logging on / off
# default is ON
LOGGING = True 
# the default client logfile
# it will be created if it doesnt exist
log = "client_log.txt"
# the default client error logfile
# it will be created if it doesnt exist
err_log = "client_error_log.txt"

# default settings
# the server / host and the port to connect ( edit for your need )
host, port = '192.168.1.54', 6001
#HOST, PORT = gethostbyname(gethostname()), 6000
# a list to manage the nicknames
nicks = [""]
# for the connection status
status = ["DISCONNECTED"]
# the standart close message for the client
close_msg = "See you later."
#servers list
servers=[(host, port)]

#check recieved message
def unpack_msg(msg):
    if len(msg) == 256:
        splitted=msg.split("#",5)
        if splitted[1] == "dat":
            splitted[5] = splitted[5].rstrip()
            if int(splitted[2]) == (len(splitted[3]) + len(splitted[5])) :
                if int(splitted[4]) == 0:
                    return 0, splitted[3], ""
                else:
                    msg_crc = crc32(splitted[5])
                    if msg_crc == int(splitted[4]):
                        return 0, splitted[3], splitted[5]
                    else: return 1, "", ""
            else: return 1, "", ""
        if splitted[0] == "sys":
            pass
        else: return 1, "", ""
    else: return 1, "", ""

#packing data for sending
def pack_msg(code, text):
    msg = "#dat#"+string.zfill(str(len(code)+len(text)),3)+"#"+code+"#"+string.zfill(str(crc32(text)),10)+"#"+text
    return msg.ljust(256)

# LOGGING function
def logging(text):
        # doc
        """logging the in and output"""
        # open or create a logfile
        logg = open(log, "a+")
        # write date and text to the logfile
        logg.write(asctime()+": "+str(text)+"\n")
        # close the logfile
        logg.close()

# ERROR LOGGING function
def err_logging(text2):
        # doc
        """this is the error logging function - log what you want"""
        # open or create a logfile
        err_logg = open(err_log, "a+")
        # write date and text to the logfile
        err_logg.write(asctime()+": "+str(text2)+"\n")
        # close the logfile
        err_logg.close()

# CONNECT to a server
def connect():
    # doc
    """connect to the standart or customer server / host"""
    # are you already connected?
    if status[-1] == "CONNECTED":
        print ""
    else:
        # some global variables
        global new_connect, my_nick, nicks, data, online
        recv_message = ""
        if nicks[-1] == "":
            # got a nick name ?
            my_nick = raw_input("enter your nickname first: ")
            # add the new nickname to the list
            nicks.append(my_nick)
            # use the prompt as your nickname
            sys.ps1 = ">:"
            #host=raw_input("enter your host/IP: ")
            #port=input("enter your port: ")
        # create a socket
        new_connect = socket.socket(AF_INET, SOCK_STREAM)
        new_connect.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        while 1:
            sleep(1)
            for HOST, PORT in servers:
                sleep(1)
                try:
                    #new_connect.settimeout(0.05)
                    # connect the socket to host and port
                    new_connect.connect((HOST,PORT))
                    status.append("CONNECTED")
                    new_connect.settimeout(None)
                    break
                except socket.error, detail:
                    err_logging(detail)
                    status.append("DISCONNECTED")
                    continue
            if status[-1] == "CONNECTED":
                break
        # logging
        if LOGGING == True:
            print "connection established, logging: on\n"
        else:
            print "connection established, logging: off\n"
        try:
            # send the nickname to the server
            message=pack_msg("nnm", nicks[-1])
            new_connect.send(message)
        except socket.error, detail:
            err_logging(detail)
            status.append("DISCONNECTED")
            data.append("Connection with server lost")
        try:
            # checking the nickname
            recv_message = new_connect.recv(256)
        except socket.error, detail:
            err_logging(detail)
            status.append("DISCONNECTED")
            data.append("Connection with server lost")
        #unpack message
        err, code, check=unpack_msg(recv_message)
        # checking if the nickname is already in use (AIU)
        data.append(check)
        # set the status
        status.append("CONNECTED")
        # listening for incoming data
        # using threading for background work
        class Listen(Thread):
            def __init__(self):
                Thread.__init__(self)
            # background job
            def run(self):
                err, code, text = 0, None, None
                recv_message=""
                while 1:
                    # get and print the data
                    try:
                        #get data
                        recv_message = new_connect.recv(256)
                    except AttributeError, detail:
                        err_logging(detail)
                        status.append("DISCONNECTED")
                        new_connect.close()
                        sys.exit(0)
                        break
                    except socket.error, detail:
                        err_logging(detail)
                        status.append("DISCONNECTED")
                        data.append("Connection with server lost")
                        new_connect.close()
                        sys.exit(0)
                        break
                    #unpack data
                    err, code, text = unpack_msg(recv_message)
                    if code == "aas":
                        IP_PORT=text.split(":")
                        servers.append((IP_PORT[0],int(IP_PORT[1])))
                        continue
                    if code == "das":
                        IP_PORT=text.split(":")
                        servers.remove((IP_PORT[0],int(IP_PORT[1])))
                        continue
                    if code == "uob":
                        for i in xrange(len(online)):
                            del online[i]
                        onl_users = loads(text)
                        online.extend(onl_users)
                        continue
                    if code == "tpc":
                        topic.append(text)
                        continue
                    if err == 1:
                        err_logging("wrong packet from server")
                        status.append("DISCONNECTED")
                        new_connect.close()
                        sys.exit(0)
                        break
                    if code == "skm":
                        data.append("you were kicked from the server")
                        status.append("DISCONNECTED")
                        new_connect.close()
                        sys.exit(0)
                        break
                        # logging
                    if LOGGING == True:
                        logging(text)
                    # if no data comes or the connection is broken
                    if not text: 
                        status.append("DISCONNECTED")
                        new_connect.close()
                        sys.exit(0)
                        break
                    elif code == "bdc" or code == "psm": 
                        data.append(text)
                    
        # the needed instance for threading
        l = Listen()
        l.setDaemon(True)
        # start the background job
        l.start()

# SEND a message
def send(msg = ""):
    # doc
    """send a message to the chatserver"""
    #are you connected?
    if status[-1] == "DISCONNECTED":
        print "you are not connected"
    else:
        #parse msg
        if msg[0] == "#":
            msg_splt=msg.split(" ", 1)
            if msg_splt[0] == "#help":
                code = "hlp"
            if msg_splt[0] == "#nick":
                code = "nck"
            if msg_splt[0] == "#topic":
                code = "tpc"
            if msg_splt[0] == "#settopic":
                code = "stp"
            if msg_splt[0] == "#show":
                code = "onl"
            if msg_splt[0] == "#say":
                code = "say"
            msg_splt.append("")
        else: 
            code = "say"
            msg_splt=["say", msg]
        #pack message
        message = pack_msg(code, msg_splt[1])
        try:
            #send message
            new_connect.send(message)
        except socket.error,  detail:
            err_logging(detail)
            status.append("DISCONNECTED")
            data.append("Connection with server lost")
        except NameError,  detail:
            err_logging(detail)

def disconnect():
    status.append("DISCONNECTED")
    del servers
    try:
        new_conect.close()
    except: pass
#EOF
